package com.wkk.log.aop;

import com.wkk.annotation.AutoLog;
import com.wkk.authentication.dto.UserLoginDto;
import com.wkk.authentication.entity.UmsUser;
import com.wkk.authentication.service.UmsUserService;
import com.wkk.constant.CommonConstant;
import com.wkk.log.common.LogConstant;
import com.wkk.log.entity.UserOperation;
import com.wkk.log.service.LogService;
import com.wkk.utils.*;
import com.wkk.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * @author : WangKeKe
 * @date : 2020/8/22 18:21
 * Description : 用户记录用户操作日志
 */

@Aspect
@Slf4j
@Configuration
public class OperationAopLog {

    @Autowired
    private LogService logService;

    @Autowired
    private UmsUserService umsUserService;

    @Autowired
    private RedisUtil redisUtil;

    @Pointcut("@annotation(com.wkk.annotation.AutoLog)")
    public void autoLogPointcut() {
    }

    @Around("autoLogPointcut()")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long time = System.currentTimeMillis() - beginTime;
        saveLog(joinPoint, time, (Result) result);
        return result;
    }

    private void saveLog(ProceedingJoinPoint joinPoint, long time, Result result) {
        String token = null;
        do {
            //获取RequestAttributes
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (NoBugTools.isEmpty(requestAttributes)) {
                log.error("参数：requestAttributes为空>>>>>" + DateUtils.formatDate());
                break;
            }
            //从获取RequestAttributes中获取HttpServletRequest的信息
            HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
            if (NoBugTools.isEmpty(request)) {
                log.error("参数:request为空>>>>>>>>>>" + DateUtils.formatDate());
                break;
            }
            // 获取请求头中的token
            token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
        } while (false);

        UmsUser umsUser = null;
        if (NoBugTools.isNotEmpty(token)) {
            // 如果token不为空 则获取token中的用户id
            String userId = JwtUtil.getUserId(token);
            if (redisUtil.hasKey(CommonConstant.PREFIX_USER + userId)) {
                // 如果存在缓存则查询缓存
                umsUser = (UmsUser) redisUtil.get(CommonConstant.PREFIX_USER + userId);
                log.info("本次日志用户查询 >>>>> 缓存");
            } else {
                umsUser = umsUserService.getById(userId);
                if (NoBugTools.isNotEmpty(umsUser)) {
                    // 设置缓存
                    redisUtil.set(CommonConstant.PREFIX_USER + umsUser.getId(), umsUser, CommonConstant.REFRESH_TOKEN_TIME);
                }
            }
            if (NoBugTools.isNotEmpty(umsUser)) {
                // 检验token是否失效 - 如果失效则判定为游客操作
                if (!JwtUtil.verify(token, userId, umsUser.getPassword())) {
                    umsUser = null;
                    log.info("日志AOP验证当前token状态>>>>>失效");
                }
            }
        }
        // 赋予操作者用户名
        String username = NoBugTools.isEmpty(umsUser) ? "guest" : umsUser.getUsername();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 注解描述
        AutoLog autoLog = method.getAnnotation(AutoLog.class);
        // 类路径
        String className = joinPoint.getTarget().getClass().getName();
        // 方法名
        String methodName = signature.getName();
        //参数名
        String[] strings = signature.getParameterNames();
        //参数值
        Object[] methodArgs = joinPoint.getArgs();
        // 用于拼接字符串
        StringBuilder stringBuilder = new StringBuilder();
        // 用于截取拼接好后的字符串
        String params = "";
        if (strings.length > 0) {
            // 判断是否有参数
            for (int i = 0; i < strings.length; i++) {
                if (strings[i].equals(LogConstant.HTTP_SERVLET_RESPONSE) ||
                        strings[i].equals(LogConstant.HTTP_SERVLET_REQUEST)) {
                    // 过滤 HttpServletRequest 和 HttpServletResponse
                    continue;
                }
                if (strings[i].equals(LogConstant.USER_LOGIN_DTO)) {
                    // - 这里可以判断 是登录接口 - 故操作者不为游客 而是登陆者
                    // - 还能判断是否是注册接口 - 但是只有注册成功时 才会将操作这赋予用户名
                    // 防止密码泄露
                    UserLoginDto userLoginDto = (UserLoginDto) methodArgs[i];
                    userLoginDto.setPassword("********");
                    methodArgs[i] = userLoginDto;
                    // 如果登录成功，则将操作者每次修改为当前登录的用户
                    if (result.getCode().equals(200)) {
                        username = userLoginDto.getUsername();
                    }
                }
                stringBuilder.append(strings[i]).append(":").append(methodArgs[i]).append(",");
            }
            // 移除最后一个逗号
            params = stringBuilder.substring(0, stringBuilder.length() - 1);
        }
        log.info("参数内容" + params);

        UserOperation userOperation = new UserOperation()
                .setOperation(autoLog.description())
                .setUsername(username)
                .setTime(time)
                .setMethod(className + "." + methodName + "()")
                .setParams(params)
                .setIp(NoBugTools.getIpAddressByRequest(SpringContextUtils.getHttpServletRequest()))
                .setStatus(result.isFlag() ? "true" : "false");
        logService.save(userOperation);
        log.info("日志加入成功！>>>>日志编号：" + userOperation.getId());
    }
}
